#pragma once

// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license

// Action.h : interface of the Action class
//


#include "Node.h"
#include "Geometry.h"
#include "Primitive.h"
#include "TransformNode.h"
#include "LightNode.h"
#include "Material.h"
#include "ShaderProgram.h"
#include "Mesh.h"
#include <memory>
#include <string>
class NodeManager;
class GeometryNode;
class World;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
// NOTE all the actions take their nodes or materials by ref. counted 
// pointer because they keep the ownership of the object for a while (a 
// long while if the user doesn't UNDO)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
// TODO using ReplaceShapeAction for this like rescaling or rotating
// is elegant and leads to great code reuse but it has a problem: 
// controls like SceneTreeView that have to be rebuilt on scene
// structural changes are rebuilt too much (which is especially
// awful on things like scaling through a spinner control)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

class Action
{
public:

    Action();

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

    // The action manager passes the world to the actions at performance time 
    // because otherwise they would have to store it themselves at creation 
    // time, and that involves more boiler-plate
    virtual void perform(World& w) = 0;
    virtual void revert(World& w) = 0;

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

    // Action descriptions are used by the logging sub-system
    virtual std::string description() const = 0;
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

class RemoveAllNodesAction : public Action
{
public:
    RemoveAllNodesAction(NodeManager& nm);

protected:

    void perform(World& w) override;
    void revert(World& w) override;

    std::string description() const override;

    NodeTree m_treeCopy;
    NodeManager& m_nodeManager;
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

// We need to provide a separate PropertyType template parameter (which most
// subclasses won't use) because sometimes the "get" type of a property is
// const, whereas the "set" type is not. For an example, see the 
// "PrimitiveTypeAction" subclass

template<class Derived, class ItemType, class PropertyType>
class PropertyAction : public Action
{
public:

    // Constructor with two parameters. The initial value is read from the 
    // using the getValue mixin function of Derived

    PropertyAction(const ItemType& item, const PropertyType& v)
    : m_item(item), m_finalValue(v), m_iniValueComputed(false)
    {
        
    }

    // Constructor with three parameters. The initial value is passed
    // explicitly. The mixin class Derived does not need to provide
    // a getValue mixin function if it uses this constructor only

    PropertyAction(const ItemType& item, const PropertyType& v, 
                       const PropertyType& iniValue)
    : m_item(item), m_finalValue(v), m_iniValue(iniValue), m_iniValueComputed(true)
    {
    }

protected:
    
    // Action overrides
    void perform(World&) override
    {
        if(!m_iniValueComputed)
        {
            m_iniValueComputed = true;
            m_iniValue = ((Derived*)this)->getValue();
        }

        ((Derived*)this)->setValue(m_finalValue);
    }
    void revert(World&) override
    {
        ASSERT(m_iniValueComputed);
        ((Derived*)this)->setValue(m_iniValue);
    }

    ItemType m_item;
    PropertyType m_finalValue;
    PropertyType m_iniValue;
    bool m_iniValueComputed;
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

class TransformNodeAction 
    : public PropertyAction<TransformNodeAction, ref<TransformNode>, gx::Mat4>
{
public:

    // Warning: transforms are local
    TransformNodeAction(ref<TransformNode> node, const gx::Mat4& finalMat,
                        NodeEvent::Property prop=NodeEvent::NodeTransform);
    TransformNodeAction(ref<TransformNode> node, const gx::Mat4& finalMat, 
                        const gx::Mat4& iniMat,
                        NodeEvent::Property prop=NodeEvent::NodeTransform);

    void setValue(const gx::Mat4& v) const;
    gx::Mat4 getValue() const;

protected:

    NodeEvent::Property m_changedProp;
    std::string description() const override; 
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

//class ReplaceShapeAction : public Action
//{
//public:
//
//    ReplaceShapeAction(const Node & block, ref<Geometry> newShape);
//    
//protected:
//    
//    std::string description() const override;
//    void perform(World& w) override;
//    void revert(World& w) override;
//
//    ref<Node> m_block;
//    ref<Geometry> m_newShape;
//    ref<Geometry> m_previousShape;
//};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 


//class InsertBlockAction : public Action
//{
//public:
//
//    InsertBlockAction(ref<Node> block);
//    
//protected:
//
//    std::string description() const override;
//
//    // TODO this assumes that insert operations are not
//    // destructive (i.e., that the destination block was
//    // not previously occupied). If that is not the case
//    // we should store the previous shape and restore it
//    // upon revert.
//    // Once we have done that, we can implement EraseBlockAction
//    // as the "inverse" of InsertBlockAction
//
//    void perform(World& w) override;
//    void revert(World& w) override;
//
//    ref<Node> m_block;
//};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

//class EraseBlockAction : public Action
//{
//public:
//
//    EraseBlockAction(const Node& block);
//    
//protected:
//    
//    std::string description() const override;
//    void perform(World& w) override;
//    void revert(World& w) override;
//
//    ref<Node> m_block;
//    ref<Geometry> m_previousShape;
//};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 


class ShapeWireframeAction 
    : public PropertyAction<ShapeWireframeAction, ref<MeshBufferInstance>, bool>
{
public:

    ShapeWireframeAction(ref<MeshBufferInstance> mbi, bool v);
    
    void setValue(bool v) const;
    bool getValue() const;

protected:

    std::string description() const override; 
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 


class ShapeMaterialIdAction 
    : public PropertyAction<ShapeMaterialIdAction, ref<MeshBufferInstance>, 
                            ref<Material> >
{
public:

    ShapeMaterialIdAction(ref<MeshBufferInstance> node, ref<Material> v, 
                          GeometrySide side);
    
    void setValue(ref<Material> v) const;
    ref<Material> getValue() const;

protected:

    GeometrySide m_side;
    std::string description() const override; 
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 


class ShapeShaderIdAction 
    : public PropertyAction<ShapeShaderIdAction, ref<MeshBufferInstance>, 
                            ref<ShaderProgram> >
{
public:

    ShapeShaderIdAction(ref<MeshBufferInstance> node, ref<ShaderProgram> v);
    
    void setValue(ref<ShaderProgram> v) const;
    ref<ShaderProgram> getValue() const;

protected:

    std::string description() const override; 
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

class PrimitiveTypeAction 
    : public PropertyAction<PrimitiveTypeAction, ref<GeometryNode>, ref<Primitive> >
{
public:

    PrimitiveTypeAction(ref<GeometryNode> node, ref<Primitive> v);
    
    void setValue(ref<Primitive> v) const;
    ref<Primitive> getValue() const;

protected:

    std::string description() const override;
};


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

class LightParamsAction 
    : public PropertyAction<LightParamsAction , ref<LightNode>, LightParams>
{
public:

    LightParamsAction(ref<LightNode> node, const LightParams& v);

    void setValue(const LightParams& v) const;
    LightParams getValue() const;

protected:

    std::string description() const override; 
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 


class MaterialParamsAction 
    : public PropertyAction<MaterialParamsAction , ref<Material>, MaterialParams>
{
public:

    MaterialParamsAction(ref<Material> node, const MaterialParams& v);

    void setValue(const MaterialParams& v) const;
    MaterialParams getValue() const;

protected:

    std::string description() const override; 
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
